home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-04 / timer11.zip / LZTIMER.ASM < prev    next >
Assembly Source File  |  1992-04-21  |  23KB  |  778 lines

  1. ;****************************************************************************
  2. ;*
  3. ;*                           Long Period Zen Timer
  4. ;*
  5. ;*                               From the book
  6. ;*                         "Zen of Assembly Language"
  7. ;*                            Volume 1, Knowledge
  8. ;*
  9. ;*                             by Michael Abrash
  10. ;*
  11. ;*                      Modifications by Kendall Bennett
  12. ;*
  13. ;* Filename:    $RCSfile: lztimer.asm $
  14. ;* Version:        $Revision: 1.4 $
  15. ;*
  16. ;* Language:    8086 Assembler
  17. ;* Environment:    IBM PC (MS DOS)
  18. ;*
  19. ;* Description:    Uses the 8253 timer and the BIOS time-of-day count to time
  20. ;*                the performance of code that takes less than an hour to
  21. ;*                execute.
  22. ;*
  23. ;*                Because interrupts are left on (in order to allow the timer
  24. ;*                interrupt to be recognised), this is less accurate than the
  25. ;*                precision Zen Timer, so it is best used only to time code
  26. ;*                that takes more than about 54 milliseconds to execute (code
  27. ;*                that causes the precision Zen Timer to overflow). Resolution
  28. ;*                is limited by the occurrence of timer interrupts.
  29. ;*
  30. ;*    Externally 'C' callable routines:
  31. ;*
  32. ;*    LZTimerOn:        Saves the BIOS time of day count and starts the
  33. ;*                    long period Zen Timer.
  34. ;*
  35. ;*    LZTimerOff:        Stops the long-period Zen Timer and saves the timer
  36. ;*                    count and the BIOS time of day count.
  37. ;*
  38. ;*    LZTimerReport:    Prints the net time that passed between starting and
  39. ;*                    stopping the timer.
  40. ;*
  41. ;*    LZTimerCount:    Returns an unsigned long representing the timed count
  42. ;*                    in microseconds. If more than an hour passed or
  43. ;*                    midnight passed during the timing interval, LZTimerCount
  44. ;*                    will return the value 0xFFFFFFFF (an invalid count).
  45. ;*
  46. ;*    Note:    If either more than an hour passes or midnight falls between
  47. ;*            calls to LZTimerOn and LZTimerOff, an error is reported. For
  48. ;*            timing code that takes more than a few minutes to execute,
  49. ;*            either the DOS TIME command in a batch file before and after
  50. ;*            execution of the code to time or the use of the DOS time-of-day
  51. ;*            function in place of the long-period Zen Timer is more than
  52. ;*            adequate.
  53. ;*
  54. ;*    Note:    The PS/2 version is assembled by setting the symbol PS2 to 1.
  55. ;*            PS2 must be set to 1 on PS/2 computers because the PS/2's timers
  56. ;*            are not compatible with an undocumented timer-stopping feature
  57. ;*            of the 8253; the alternative timing approach that must be used
  58. ;*            on PS/2 computers leaves a short window during which the timer 0
  59. ;*            count and the BIOS timer count may not be synchronised. You
  60. ;*            should also set the PS2 symbol to 1 if you're getting erratic
  61. ;*            or obviously incorrect results.
  62. ;*
  63. ;*    Note:    When PS2 is 0, the code relies on a undocumented feature of the
  64. ;*            8253 to get more reliable readings. It is possible that the
  65. ;*            8253 (or whatever chip is emulating the 8253) may be put into
  66. ;*            an undefined or incorrect state when this feature if used.
  67. ;*
  68. ;*        *****************************************************************
  69. ;*        * If your computer displays any hint of erratic behaviour        *
  70. ;*        * after the long-period Zen Timer is used, such as the floppy    *
  71. ;*        * drive failing to operate properly, reboot the system, set        *
  72. ;*        * PS2 to 1 and leave it that way!                                *
  73. ;*        *****************************************************************
  74. ;*
  75. ;*    Note:    Each block of code being timed should ideally be run several
  76. ;*            times, with at least two similar readings required to
  77. ;*            establish a true measurement, in order to eliminate any
  78. ;*            variability caused by interrupts.
  79. ;*
  80. ;*    Note:    Interrupts must not be disabled for more than 54 ms at a
  81. ;*            stretch during the timing interval. Because interrupts are
  82. ;*            enabled, key, mice, and other devices that generate interrupts
  83. ;*            should not be used during the timing interval.
  84. ;*
  85. ;*    Note:    Any extra code running off the timer interrupt (such as
  86. ;*            some memory resident utilities) will increase the time
  87. ;*            measured by the Zen Timer.
  88. ;*
  89. ;*    Note:    These routines can introduce inaccuracies of up to a few
  90. ;*            tenths of a second into the system clock count for each
  91. ;*            code section being timed. Consequently, it's a good idea to
  92. ;*            reboot at the conclusion of timing sessions. (The
  93. ;*            battery-backed clock, if any, is not affected by the Zen
  94. ;*            timer.)
  95. ;*
  96. ;*  All registers and all flags are preserved by all routines.
  97. ;*
  98. ;* $Id: lztimer.asm 1.4 92/04/20 17:33:43 kjb release $
  99. ;*
  100. ;* Revision History:
  101. ;* -----------------
  102. ;*
  103. ;* $Log:    lztimer.asm $
  104. ;* Revision 1.4  92/04/20  17:33:43  kjb
  105. ;* Modified to allow timing across a midnight boundary
  106. ;* 
  107. ;* Revision 1.3  92/01/27  21:39:57  kjb
  108. ;* Converted to a memory model independant library, and released to the
  109. ;* public.,
  110. ;* 
  111. ;* Revision 1.2  91/11/16  17:11:33  kjb
  112. ;* Modified to return a long integer count rather than a string.
  113. ;*
  114. ;* Revision 1.1  91/11/14  17:16:28  kjb
  115. ;* Initial revision
  116. ;*
  117. ;****************************************************************************
  118.  
  119.         IDEAL
  120.  
  121. INCLUDE "model.mac"                ; Memory model macros
  122.  
  123. header    lztimer                    ; Set up memory model
  124.  
  125. ;****************************************************************************
  126. ;
  127. ; Equates used by long period Zen Timer
  128. ;
  129. ;****************************************************************************
  130.  
  131. ; Set PS2 to 0 to assemble for use on a fully 8253 compatible system. Set
  132. ; it to 1 for PS/2 computers and partially compatible 8253 systems.
  133.  
  134. PS2                equ        1
  135.  
  136. ; Base address of 8253 timer chip
  137.  
  138. BASE_8253        =        40h
  139.  
  140. ; The address of the timer 0 count registers in the 8253
  141.  
  142. TIMER_0_8253    =        BASE_8253 + 0
  143.  
  144. ; The address of the mode register in the 8253
  145.  
  146. MODE_8253        =        BASE_8253 + 3
  147.  
  148. ; The address of the BIOS timer count variable in the BIOS data area.
  149.  
  150. TIMER_COUNT        =        46Ch
  151.  
  152. ; Macro to emulate a POPF instruction in order to fix the bug in some
  153. ; 80286 chips which allows interrupts to occur during a POPF even when
  154. ; interrupts are disabled.
  155.  
  156. macro    MPOPF
  157.         local    p1,p2
  158.         jmp        short p2
  159. p1:        iret                    ; Jump to pushed address & pop flags
  160. p2:        push    cs                ; construct far return address to
  161.         call    p1                ;  the next instructionz
  162. endm
  163.  
  164. ; Macro to delay briefly to ensure that enough time has elapsed between
  165. ; successive I/O accesses so that the device being accessed can respond
  166. ; to both accesses even on a very fast PC.
  167.  
  168. macro    DELAY
  169.         jmp        $+2
  170.         jmp        $+2
  171.         jmp        $+2
  172. endm
  173.  
  174. begcodeseg    lztimer                ; Start of code segment
  175.  
  176. StartBIOSCountLow    dw    ?        ; BIOS count low word at the start of
  177.                                 ;  the timing period
  178. StartBIOSCountHigh    dw    ?        ; BIOS count high word at the start of
  179.                                 ;  the timing period
  180. EndBIOSCountLow        dw    ?        ; BIOS count low word at the end of
  181.                                 ;  the timing period
  182. EndBIOSCountHigh    dw    ?        ; BIOS count high word at the end of
  183.                                 ;  the timing period
  184. EndTimedCount        dw    ?        ; Timer 0 count at the end of
  185.                                 ;  the timing period
  186. ReferenceCount        dw    ?        ; Number of counts required to execute timer
  187.                                 ;  overhead code
  188.  
  189. ; String printed to report results.
  190.  
  191. label            OutputStr byte
  192.                 db        0dh, 0ah, 'Timed count: '
  193. TimedCountStr    db        10 dup (?)
  194.                 db        ' microseconds', 0ah, 0dh
  195.                 db        '$'
  196.  
  197. ; Temporary storage for timed count as it's divided down by powers
  198. ; of ten when converting from doubleword binary to ASCII
  199.  
  200. CurrentCountLow        dw    ?
  201. CurrentCountHigh    dw    ?
  202.  
  203. ; Powers of ten table use to perform division by 10 when doing doubleword
  204. ; conversion from binary to ASCII.
  205.  
  206. label    PowersOfTen word
  207.         dd        1
  208.         dd        10
  209.         dd        100
  210.         dd        1000
  211.         dd        10000
  212.         dd        100000
  213.         dd        1000000
  214.         dd        10000000
  215.         dd        100000000
  216.         dd        1000000000
  217. label    PowersOfTenEnd word
  218.  
  219. ; String printed to report that the high word of the BISO count changed
  220. ; while timing (an hour elapsed or midnight was crossed),
  221. ; and so the count is invalid and the test needs to be rerun.
  222.  
  223. label    TurnOverStr byte
  224.         db    0dh,0ah
  225.         db    '****************************************************',0dh,0ah
  226.         db    '* Either midnight passed or an hour or more passed *',0dh,0ah
  227.         db    '* while timing was in progress. If the former was  *',0dh,0ah
  228.         db    '* the case, please re-run the test; if the latter  *',0dh,0ah
  229.         db    '* was the case, the test code takes too long to    *',0dh,0ah
  230.         db    '* run to be timed by the long-period Zen Timer.    *',0dh,0ah
  231.         db    '* Suggestions: Use the DOS TIME command, the DOS   *',0dh,0ah
  232.         db    '* time function, or a watch!                       *',0dh,0ah
  233.         db    '****************************************************',0dh,0ah
  234.         db    '$'
  235.  
  236. ;----------------------------------------------------------------------------
  237. ; void LZTimerOn(void);
  238. ;----------------------------------------------------------------------------
  239. ; Starts the Long period Zen timer counting.
  240. ;----------------------------------------------------------------------------
  241. procfar        _LZTimerOn
  242.  
  243. ; Save the context of the program being timed
  244.  
  245.         push    ax
  246.         pushf
  247.  
  248. ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  249. ; linear counting rather than count-by-two counting. Also stops
  250. ; timer 0 until the timer count is loaded, except on PS/2 computers.
  251.  
  252.         mov        al,00110100b        ; mode 2
  253.         out        MODE_8253,al
  254.  
  255. ; Set the timer count to 0, so we know we won't get another timer
  256. ; interrupt right away. Note: this introduces and inaccuracy of up to 54 ms
  257. ; in the system clock count each time it is executed.
  258.  
  259.         DELAY
  260.         sub        al,al
  261.         out        TIMER_0_8253,al        ; lsb
  262.         DELAY
  263.         out        TIMER_0_8253,al        ; msb
  264.  
  265. ; In case interrupts are disabled, enable interrupts briefly to allow the
  266. ; interrupt generated when switching from mode 3 to mode 2 to be recognised.
  267. ; Interrupts must be enabled for at least 210 ns to allow time for that
  268. ; interrupt to occur. Here, 10 jumps are used for the delay to ensure that
  269. ; the delay time will be more than enough even on a very fast PC.
  270.  
  271.         pushf
  272.         sti
  273.         rept    10
  274.         jmp        $+2
  275.         endm
  276.         MPOPF
  277.  
  278. ; Store the timing start BIOS count.
  279. ; (Since the timer count was just set to 0, the BIOS count will stay the
  280. ; same for the next 54 ms, so we don't need to disable interrupts in order
  281. ; to avoid getting a half-changed count.)
  282.  
  283.         push    ds
  284.         sub        ax,ax
  285.         mov        ds,ax
  286.         mov        ax,[ds:TIMER_COUNT+2]
  287.         mov        [StartBIOSCountHigh],ax
  288.         mov        ax,[ds:TIMER_COUNT]
  289.         mov        [StartBIOSCountLow],ax
  290.         pop        ds
  291.  
  292. ; Set the timer count to 0 again to start the timing interval.
  293.  
  294.         mov        al,00110100b        ; set up to load initial
  295.         out        MODE_8253,al        ; timer count
  296.         DELAY
  297.         sub        al,al
  298.         out        TIMER_0_8253,al        ; load count lsb
  299.         DELAY
  300.         out        TIMER_0_8253,al        ; load count msb
  301.  
  302. ; Restore the context and return.
  303.  
  304.         MPOPF                        ; keeps interrupts off
  305.         pop        ax
  306.         ret
  307.  
  308. procend        _LZTimerOn
  309.  
  310. ;----------------------------------------------------------------------------
  311. ; void LZTimerOff(void);
  312. ;----------------------------------------------------------------------------
  313. ; Stops the long period Zen timer and saves count.
  314. ;----------------------------------------------------------------------------
  315. procfar        _LZTimerOff
  316.  
  317. ; Save the context of the program being timed
  318.  
  319.         pushf
  320.         push    ax
  321.         push    cx
  322.  
  323. ; In case interrupts are disabled, enable interrupts briefly to allow
  324. ; any pending interrupts to be handled. Interrupts must be enabled for at
  325. ; leas 210 ns to allow time for that interrupt to occur. Here, 10 jumps
  326. ; are used for the delay to ensure that the delay will be more than long
  327. ; enough even on a very fast PC.
  328.  
  329.         sti
  330.         rept    10
  331.         jmp        $+2
  332.         endm
  333.  
  334. ; Latch the timer count.
  335.  
  336. if PS2
  337.  
  338.         mov        al,00000000b        ; latch timer 0
  339.         out        MODE_8253,al
  340.  
  341. ; This is where a one instruction long window exists on the PS/2. The timer
  342. ; count and the BIOS count can lose synchronization; since the timer keeps
  343. ; counting after it's latched, it can turn over right after it's latched
  344. ; and cause the BIOS count to turn over before interrupts are disabled,
  345. ; leaving us with a timer count from before the timer turned over coupled
  346. ; with a BIOS count from after the timer turned over. The result is a count
  347. ; that's 54 ms too long.
  348.  
  349. else
  350.  
  351. ; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count load,
  352. ; which stops timer 0 until the count is loaded. (Only works on fully
  353. ; 8253 compatible chips).
  354.  
  355.         mov        al,00110100b        ; Mode 2
  356.         out        MODE_8253,al
  357.         DELAY
  358.         mov        al,00000000b        ; Latch timer 0 count
  359.         out        MODE_8253,al
  360.  
  361. endif
  362.  
  363.         cli                            ; Stop the BIOS count
  364.  
  365. ; Read the BIOS count. (Since interrupts are disabled, the BIOS
  366. ; count won't change).
  367.  
  368.         push    ds
  369.         sub        ax,ax
  370.         mov        ds,ax
  371.         mov        ax,[ds:TIMER_COUNT+2]
  372.         mov        [EndBIOSCountHigh],ax
  373.         mov        ax,[ds:TIMER_COUNT]
  374.         mov        [EndBIOSCountLow],ax
  375.         pop        ds
  376.  
  377. ; Read out the count we latched earlier.
  378.  
  379.         in        al,TIMER_0_8253        ; least significant byte
  380.         DELAY
  381.         mov        ah,al
  382.         in        al,TIMER_0_8253        ; most significant byte
  383.         xchg    ah,al
  384.         neg        ax                    ; Convert from countdown remaining
  385.                                     ;  to elapsed count
  386.         mov        [EndTimedCount],ax
  387.  
  388. ; Restart timer 0, which is still waiting for an initial count
  389. ; to be loaded.
  390.  
  391. ife    PS2
  392.  
  393.         DELAY
  394.         mov        al,00110100b        ; mode 2, waiting to load a 2 byte count
  395.         out        MODE_8253,al
  396.         DELAY
  397.         sub        al,al
  398.         out        TIMER_0_8253,al        ; lsb
  399.         DELAY
  400.         mov        al,ah
  401.         out        TIMER_0_8253,al        ; msb
  402.  
  403. endif
  404.  
  405.         sti                            ; Let the BIOS count continue
  406.  
  407. ; Time a zero-length code fragment, to get a reference count for how
  408. ; much overhead this routine has. Time it 16 times and average it, for
  409. ; accuracy, rounding the result.
  410.  
  411.         mov        [ReferenceCount],0
  412.         mov        cx,16
  413.         cli                            ; interrupts off to allow a precise
  414.                                     ;  reference count
  415. @@RefLoop:
  416.         call    ReferenceTimerOn
  417.         call    ReferenceTimerOff
  418.         loop    @@RefLoop
  419.         sti
  420.         add        [ReferenceCount],8    ; total + (0.5 * 16)
  421.         mov        cl,4
  422.         shr        [ReferenceCount],cl    ; (total) / 16 + 0.5
  423.  
  424. ; Restore the context of the program being timed and return to it.
  425.  
  426.         pop        cx
  427.         pop        ax
  428.         MPOPF
  429.         ret
  430.  
  431. procend        _LZTimerOff
  432.  
  433. ;----------------------------------------------------------------------------
  434. ; ReferenceTimerOn
  435. ;----------------------------------------------------------------------------
  436. ; Called by PZTimerOff to start timer for overhead measurements.
  437. ;----------------------------------------------------------------------------
  438. proc        ReferenceTimerOn far
  439.  
  440. ; Save the context of the program being timed
  441.  
  442.         push    ax
  443.         pushf                        ; Interrupts are already off
  444.  
  445. ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  446. ; linear counting rather than count-by-two counting.
  447.  
  448.         mov        al,00110100b        ; set up to load
  449.         out        MODE_8253,al        ; timer count
  450.         DELAY
  451.  
  452. ; Set the timer count to 0
  453.  
  454.         sub        al,al
  455.         out        TIMER_0_8253,al        ; load count lsb
  456.         DELAY
  457.         out        TIMER_0_8253,al        ; load count msb
  458.  
  459. ; Restore the context and return.
  460.  
  461.         MPOPF
  462.         pop        ax
  463.         ret
  464.  
  465. procend        ReferenceTimerOn
  466.  
  467. ;----------------------------------------------------------------------------
  468. ; ReferenceTimerOff
  469. ;----------------------------------------------------------------------------
  470. ; Called by PZTimerOff to stop timer and add result to ReferenceCount
  471. ; for overhead measurements.
  472. ;----------------------------------------------------------------------------
  473. proc        ReferenceTimerOff far
  474.  
  475. ; Save the context of the program being timed
  476.  
  477.         pushf
  478.         push    ax
  479.         push    cx
  480.  
  481. ; Match the interrupt window delay in LZTimerOff
  482.  
  483.         sti
  484.         rept    10
  485.         jmp        $+2
  486.         endm
  487.  
  488. ; Latch the count and read it.
  489.  
  490.         mov        al,00000000b        ; latch timer 0
  491.         out        MODE_8253,al
  492.         DELAY
  493.         in        al,TIMER_0_8253        ; least significant byte
  494.         DELAY
  495.         mov        ah,al
  496.         in        al,TIMER_0_8253        ; most significant byte
  497.         xchg    ah,al
  498.         neg        ax                    ; Convert from countdown remaining
  499.                                     ;  to elapsed count
  500.         add        [ReferenceCount],ax
  501.  
  502. ; Restore the context of the program being timed and return to it.
  503.  
  504.         pop        cx
  505.         pop        ax
  506.         MPOPF
  507.         ret
  508.  
  509. procend        ReferenceTimerOff
  510.  
  511. ;----------------------------------------------------------------------------
  512. ; void LZTimerReport(void);
  513. ;----------------------------------------------------------------------------
  514. ; Report timing results found.
  515. ;----------------------------------------------------------------------------
  516. procfar        _LZTimerReport
  517.  
  518.         pushf
  519.         push    ax
  520.         push    bx
  521.         push    cx
  522.         push    dx
  523.         push    si
  524.         push    di
  525.         push    ds
  526.  
  527.         push    cs                    ; DOS functions require that DS point
  528.         pop        ds                    ;  to text to be displayed on the screen
  529.  
  530. if codesize
  531.         ASSUME    ds:lztimer_TEXT
  532. else
  533.         ASSUME    ds:_TEXT
  534. endif
  535.  
  536. ; See if a midnight boundary has passed and adjust the finishing BIOS
  537. ; count by the number of ticks in 24 hours. We wont be able to detect
  538. ; more than 24 hours, but at least we can time across a midnight
  539. ; boundary
  540.  
  541.         mov        ax,[EndBIOSCountHigh]    ; Is end < start?
  542.         cmp        ax,[StartBIOSCountHigh]
  543.         ja        @@CheckForHour            ; No, check for hour passing
  544.         jb        @@Adjust                ; Yes, adjust ending time
  545.  
  546.         mov        ax,[EndBIOSCountLow]    ; Maybe, check low order words
  547.         cmp        ax,[StartBIOSCountLow]
  548.         jae        @@CheckForHour            ; No, check for hour passing
  549.  
  550. ; Adjust the finishing time by adding the number of ticks in 24 hours
  551. ; (1573040).
  552.  
  553. @@Adjust:
  554.         add        [EndBIOSCountLow],00B0h
  555.         adc        [EndBIOSCountHigh],18h
  556.  
  557. ; See if more than an hour passed during timing. If so, notify the user.
  558.  
  559. @@CheckForHour:
  560.         mov        ax,[StartBIOSCountHigh]
  561.         cmp        ax,[EndBIOSCountHigh]
  562.         jz        @@CalcBIOSTime        ; Hour count didn't change, so
  563.                                     ;  everything is fine
  564.  
  565.         inc        ax
  566.         cmp        ax,[EndBIOSCountHigh]
  567.         jnz        @@TestTooLong        ; Two hour boundaries passed, so the
  568.                                     ;  results are no good
  569.         mov     ax,[EndBIOSCountLow]
  570.         cmp        ax,[StartBIOSCountLow]
  571.         jb        @@CalcBIOSTime        ; a single hour boundary passed. That's
  572.                                     ; OK, so long as the total time wasn't
  573.                                     ; more than an hour.
  574.  
  575. ; Over an hour elapsed passed during timing, which renders
  576. ; the results invalid. Notify the user. This misses the case where a
  577. ; multiple of 24 hours has passed, but we'll rely on the perspicacity of
  578. ; the user to detect that case :-).
  579.  
  580. @@TestTooLong:
  581.         mov        ah,9
  582.         mov        dx,offset TurnOverStr
  583.         int        21h
  584.         jmp        short @@Done
  585.  
  586. ; Convert the BIOS time to microseconds
  587.  
  588. @@CalcBIOSTime:
  589.         mov        ax,[EndBIOSCountLow]
  590.         sub        ax,[StartBIOSCountLow]
  591.         mov        dx,54925            ; Number of microseconds each
  592.                                     ;  BIOS count represents.
  593.         mul        dx
  594.         mov        bx,ax                ; set aside BIOS count in
  595.         mov        cx,dx                ;  microseconds
  596.  
  597. ; Convert timer count to microseconds
  598.  
  599.         mov        ax,[EndTimedCount]
  600.         mov        si,8381
  601.         mul        si
  602.         mov        si,10000
  603.         div        si                    ; * 0.8381 = * 8381 / 10000
  604.  
  605. ; Add the timer and BIOS counts together to get an overall time in
  606. ; microseconds.
  607.  
  608.         add        bx,ax
  609.         adc        cx,0
  610.  
  611. ; Subtract the timer overhead and save the result
  612.  
  613.         mov        ax,[ReferenceCount]
  614.         mov        si,8381
  615.         mul        si
  616.         mov        si,10000
  617.         div        si                    ; * 0.8381 = * 8381 / 10000
  618.         sub        bx,ax
  619.         sbb        cx,0
  620.         mov        [CurrentCountLow],bx
  621.         mov        [CurrentCountHigh],cx
  622.  
  623. ; Convert the result to an ASCII string by trial subtractions of
  624. ; powers of 10.
  625.  
  626.         mov        di,(offset PowersOfTenEnd - offset PowersOfTen) - 4
  627.         mov        si,offset TimedCountStr
  628.  
  629. @@CTSNextDigit:
  630.         mov        bl,'0'
  631.  
  632. @@CTSLoop:
  633.         mov        ax,[CurrentCountLow]
  634.         mov        dx,[CurrentCountHigh]
  635.         sub        ax,[PowersOfTen+di]
  636.         sbb        dx,[PowersOfTen+di+2]
  637.         jc        @@CTSNextPowerDown
  638.         inc        bl
  639.         mov        [CurrentCountLow],ax
  640.         mov        [CurrentCountHigh],dx
  641.         jmp        @@CTSLoop
  642.  
  643. @@CTSNextPowerDown:
  644.         mov        [si],bl
  645.         inc        si
  646.         sub        di,4
  647.         jns        @@CTSNextDigit
  648.  
  649. ; Print the results.
  650.  
  651.         mov        ah,9
  652.         mov        dx,offset OutputStr
  653.         int        21h
  654.  
  655. @@Done:
  656.         pop        ds
  657.         pop        di
  658.         pop        si
  659.         pop        dx
  660.         pop        cx
  661.         pop        bx
  662.         pop        ax
  663.         MPOPF
  664.         ret
  665.  
  666.         ASSUME    ds:DGROUP
  667.  
  668. procend        _LZTimerReport
  669.  
  670. ;----------------------------------------------------------------------------
  671. ; unsigned long LZTimerCount(void);
  672. ;----------------------------------------------------------------------------
  673. ; Returns an unsigned long representing the net time in microseconds.
  674. ;
  675. ; If either and hour has passed, or midnight passed while timing, we
  676. ; return 0xFFFFFFFF as the count (which is not a possible count in itself).
  677. ;----------------------------------------------------------------------------
  678. procfar        _LZTimerCount
  679.  
  680.         setupDS
  681.  
  682. ; See if a midnight boundary has passed and adjust the finishing BIOS
  683. ; count by the number of ticks in 24 hours. We wont be able to detect
  684. ; more than 24 hours, but at least we can time across a midnight
  685. ; boundary
  686.  
  687.         mov        ax,[EndBIOSCountHigh]    ; Is end < start?
  688.         cmp        ax,[StartBIOSCountHigh]
  689.         ja        @@CheckForHour            ; No, check for hour passing
  690.         jb        @@Adjust                ; Yes, adjust ending time
  691.  
  692.         mov        ax,[EndBIOSCountLow]    ; Maybe, check low order words
  693.         cmp        ax,[StartBIOSCountLow]
  694.         jae        @@CheckForHour            ; No, check for hour passing
  695.  
  696. ; Adjust the finishing time by adding the number of ticks in 24 hours
  697. ; (1573040).
  698.  
  699. @@Adjust:
  700.         add        [EndBIOSCountLow],00B0h
  701.         adc        [EndBIOSCountHigh],18h
  702.  
  703. ; See if more than an hour passed during timing. If so, notify the user.
  704.  
  705. @@CheckForHour:
  706.         mov        ax,[StartBIOSCountHigh]
  707.         cmp        ax,[EndBIOSCountHigh]
  708.         jz        @@CalcBIOSTime        ; Hour count didn't change, so
  709.                                     ;  everything is fine
  710.  
  711.         inc        ax
  712.         cmp        ax,[EndBIOSCountHigh]
  713.         jnz        @@TestTooLong        ; Two hour boundaries passed, so the
  714.                                     ;  results are no good
  715.         mov     ax,[EndBIOSCountLow]
  716.         cmp        ax,[StartBIOSCountLow]
  717.         jb        @@CalcBIOSTime        ; a single hour boundary passed. That's
  718.                                     ; OK, so long as the total time wasn't
  719.                                     ; more than an hour.
  720.  
  721. ; Over an hour elapsed passed during timing, which renders
  722. ; the results invalid. Notify the user. This misses the case where a
  723. ; multiple of 24 hours has passed, but we'll rely on the perspicacity of
  724. ; the user to detect that case :-).
  725.  
  726. @@TestTooLong:
  727.         mov        ax,0FFFFh
  728.         mov        dx,0FFFFh
  729.         jmp        short @@Done
  730.  
  731. ; Convert the BIOS time to microseconds
  732.  
  733. @@CalcBIOSTime:
  734.         mov        ax,[EndBIOSCountLow]
  735.         sub        ax,[StartBIOSCountLow]
  736.         mov        dx,54925            ; Number of microseconds each
  737.                                     ;  BIOS count represents.
  738.         mul        dx
  739.         mov        bx,ax                ; set aside BIOS count in
  740.         mov        cx,dx                ;  microseconds
  741.  
  742. ; Convert timer count to microseconds
  743.  
  744.         mov        ax,[EndTimedCount]
  745.         mov        si,8381
  746.         mul        si
  747.         mov        si,10000
  748.         div        si                    ; * 0.8381 = * 8381 / 10000
  749.  
  750. ; Add the timer and BIOS counts together to get an overall time in
  751. ; microseconds.
  752.  
  753.         add        bx,ax
  754.         adc        cx,0
  755.  
  756. ; Subtract the timer overhead and save the result
  757.  
  758.         mov        ax,[ReferenceCount]
  759.         mov        si,8381
  760.         mul        si
  761.         mov        si,10000
  762.         div        si                    ; * 0.8381 = * 8381 / 10000
  763.         sub        bx,ax
  764.         sbb        cx,0
  765.         mov        ax,bx
  766.         mov        dx,cx
  767.  
  768. @@Done:
  769.         restoreDS
  770.  
  771.         ret
  772.  
  773. procend        _LZTimerCount
  774.  
  775. endcodeseg    lztimer
  776.  
  777.         END                        ; End of module
  778.